Keyword:Coroutine Leak,Structured Concurrency
在剛入行工作的時候,有經驗的前輩常常提醒要注意Memory Leak,如果有發生Memory Leak的問題,就可能逐漸讓App卡頓,嚴重時還會閃退.導致客戶解除安裝,降低留存率.
雖然這麼說,但是Memory Leak很不容易在開發時發現,由於對功能沒有顯著的影響,有的時候連測試環節也沒能抓到問題,最後偷渡到架上版.這個問題只能靠著Code Review進行預防,但是難免有漏網之魚.
最常看見的說法就是由於有匿名物件在內,這個匿名物件會有外部的物件隱性引用,導致外部的物件無法回收.
但是我們常常使用匿名物件,例如點擊事件的監聽,甚至DataBinding,ViewBinding等等的功能也常常用到匿名物件,這些物件為什麼不會導致Memory Leak? 同樣是匿名物件啊.
原來,導致Leak的不是匿名,而是使用到了Thread,仍然活著的Thread在JVM中是屬於Garbage Collector Root,而Garbage Collector Root 引用到的所有物件在GC的時候都不會被回收,最後就發生了Memory Leak.
這點在App上發生的更為頻繁,因為App發生事件的時機點是不可控的,也許使用者剛按下"讀取歷史資料"的功能,這功能是耗時工作,因此App生成了一個新Thread來執行.然而使用者這時突然按下了返回離開了頁面,如果沒有做好預防,這邊的剛建立的Thread就會發生Memory Leak.
資深的工程師,可以透過經驗在開發時避免這些問題,最常見的做法是在物件關閉的發生時,將所有使用到的Thread一並關閉.
但是當團隊有新成員加入,或是是剛入行的菜鳥時,就非常有可能忘記這件事,因為關閉Thread這件事是超出使用的物件範圍,而是由外部控制.
而由於Kotllin的Coroutine目前也是藉由JVM的Thread來實作,並不是真正意義上的Coroutine環境,所以這些Thread發生的問題,如果使用不當,仍然存在.
官方考慮到了這點,所以限制了Coroutine在使用時,必須要在一個CoroutineScope中,在CoroutineScope結束的時候,其中所有正在執行的Coroutine也會一並被停止,因此便不會發生Coroutine Memory Leak的問題.
回來看看我們前幾天寫的Android Coroutine內容.viewModelScope限制了這個Coroutine只能在這個ViewModel的生命週期中執行,當ViewModel的生命週期結束後,這個Scope的內容也會停止
fun fetchCafeData(city: String = "") {
viewModelScope.launch() {
val result = async { dataRepository.fetchCafesFromNetwork(city) }
cafeList.value = result.await()
}
}
和一般的Thread寫法比較一下
...
fun fetchCafeData(city: String = "") {
thread{
val result = dataRepository.fetchCafesFromNetwork(city)
}
}
...
override fun onCleared() {
super.onCleared()
stopAllThread()
}
可以發現Coroutine執行的生命週期範圍,就綁定在使用的區塊上面一行,並且不提供生命週期範圍即不給使用,這大大降低了忘記填寫釋放時機的可能性.
這樣的設計方式,Kotlin官方稱之為Structured Concurrency,結構化並行
(以下三張圖出自)
GitHub - Zewo/Venice: Coroutines, structured concurrency and CSP for Swift on macOS and Linux.
在沒有結構化並行的環境,只要一個不小心,任何一個Function內部的Thread都有可能,並且有能力可以超脫整個Function的,影響到整個環境中.這件事非常詭異,在一個Function內建立的物件可以Leak到整個Global環境中,造成管理上的麻煩.
但有了結構化並行,就能夠在使用時限制Thread的作用域,進而降低Leak的風險
Structured Concurrency還能在多個子代Thread適用同樣的規則,一並管理這些Thread,同樣的,當父輩的Thread結束時,所有子代的Thread也會一並結束.最後會形成一個美麗的Thread樹.
在Android之中,有官方提供的viewModelScope,lifecycleScope等等來協助管理,但在KMM的iOS專案之中,就沒這麼容易了,我們明天來額外做些小手腳.